vim9script noclear

sign define SignLineAdded					text=+	linehl=NONE texthl=Added
sign define SignLineModified				text=~	linehl=NONE texthl=Changed
sign define SignLineRemoved					text=_	linehl=NONE texthl=Removed
sign define SignLineModifiedRemoved			text=~_	linehl=NONE texthl=ChangedAndRemoved
sign define SignLineRemovedFirstLine		text=_^	linehl=NONE texthl=Removed
sign define SignLineRemovedAboveAndBelow	text=_- linehl=NONE texthl=Removed

var strlist: list<string>
var b_removed: bool

def SignGetChange(first: number, count: number): list<dict<any>>
	var mod_list: list<dict<any>>
	var cnt = 0
	var start = first ?? 1
	var length = count ?? 1

	while cnt < length
		mod_list->add({
			'lnum': start + cnt,
			'group': 'sign',
			'name': 'SignLineModified',
			'buffer': bufnr(),
		})

		++cnt
	endwhile

	return mod_list
enddef

def SignGetAdd(first: number, count: number): list<dict<any>>
	var add_list: list<dict<any>>
	var cnt = 0
	var start = first ?? 1
	var length = count ?? 1

	while cnt < length
		add_list->add({
			'lnum': start + cnt,
			'group': 'sign',
			'name': 'SignLineAdded',
			'buffer': bufnr(),
		})

		++cnt
	endwhile

	return add_list
enddef

def SignGetChangeDelete(first: number): list<dict<any>>
	var start = first ?? 1

	return [{
		'lnum': start,
		'group': 'sign',
		'name': 'SignLineModifiedRemoved',
		'buffer': bufnr(),
	}]
enddef

def SignGetDelete(first: number): list<dict<any>>
	if 0 == first
		b_removed = v:true
		return [{
			'lnum': 1,
			'group': 'sign',
			'name': 'SignLineRemovedFirstLine',
			'buffer': bufnr(),
		}]
	else
		return [{
			'lnum': first,
			'group': 'sign',
			'name': (first == 1 && b_removed) ? 'SignLineRemovedAboveAndBelow' : 'SignLineRemoved',
			'buffer': bufnr(),
		}]
	endif
enddef

def SignGetList(matches: list<any>): list<dict<any>>
	if matches->empty()
		return []
	endif
	var from_line = str2nr(matches[1])
	var from_count = str2nr(matches[2])
	var to_line = str2nr(matches[3])
	var to_count = str2nr(matches[4])

	if matches[2] == '0'
		return SignGetAdd(to_line, to_count)

	elseif matches[4] == '0'
		return SignGetDelete(to_line)

	elseif matches[2] == matches[4]
		return SignGetChange(to_line, to_count)

	elseif matches[2] == ''
		return SignGetChange(to_line, 1) + SignGetAdd(to_line + 1, to_count - 1)

	elseif matches[4] == ''
		return SignGetChangeDelete(to_line)

	elseif from_count < to_count
		return SignGetChange(to_line, from_count) + SignGetAdd(to_line + from_count, to_count - from_count)

	elseif from_count > to_count
		return SignGetChange(to_line, to_count - 1) + SignGetChangeDelete(to_line + to_count - 1)

	else
		echomsg $"{matches[1]} {matches[2]} {matches[3]} {matches[4]}"
	endif

	return []
enddef

def SignClear(): void
	sign_unplace('sign', {'buffer': bufnr()})
enddef

def SignSync(): void
	var b_flag: number
	var length: number
	var matches: list<string>
	var placelist: list<dict<any>>
	var pattern: string = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'

	SignClear()

	while !strlist->empty()
		var str = strlist->remove(0)
		matches = matchlist(str, pattern)
		placelist = SignGetList(matches)
		length = len(placelist)

		if length == 0
			echomsg "sign.vim: " .. str
			continue
		endif

		sign_placelist(placelist)
	endwhile

	b_removed = v:false
enddef

export def SignShow(filename: string): void
	if empty(filename)
		return
	endif

	$"git --no-pager diff  -U0 --no-ext-diff --no-color -- ./{filename}"
	->job_start({
		cwd: fnamemodify(resolve(@%), ":p:h"),
		out_cb: (ch, msg) => msg[0 : 1] != '@@' ?? strlist->add(msg),
		exit_cb: (_, _) => strlist->empty() ? SignClear() : SignSync(),
	})
enddef
